namespace NitroExplorer
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.CompilerServices;
    using System.Text;

    internal class NitroClass
    {
        public uint FATOffset;
        public uint FATSize;
        public ushort FileCount;
        public Dictionary<string, ushort> FileIDs;
        public uint FileLastEnd;
        public Dictionary<ushort, string> FileNames;
        public Dictionary<ushort, uint> FileOffsets;
        public Dictionary<ushort, uint> FileSizes;
        public uint NTOffset;
        public uint NTSize;
        private FileStream rfs;
        public string ROMFilename;
        private Stopwatch TimeThis;

        public event DirReadyD DirReady;

        public event FileReadyD FileReady;

        public event ROMIsReadyD ROMIsReady;

        public byte[] ExtractFile(ushort FileID)
        {
            this.rfs = new FileStream(this.ROMFilename, FileMode.Open, FileAccess.Read, FileShare.Read);
            this.rfs.Seek((long) ((ulong) this.FileOffsets[FileID]), SeekOrigin.Begin);
            byte[] buffer = new byte[this.FileSizes[FileID]];
            this.rfs.Read(buffer, 0, (int)this.FileSizes[FileID]);
            this.rfs.Dispose();
            return buffer;
        }

        private void LoadDir(string DirName, ushort DirID, ushort Parent)
        {
            long position = this.rfs.Position;
            this.rfs.Seek(this.NTOffset + (8 * (DirID & 0xfff)), SeekOrigin.Begin);
            uint num2 = this.ReadUInt(this.rfs);
            ushort num3 = this.ReadUShort(this.rfs);
            this.ReadUShort(this.rfs);
            if (Parent == 0)
            {
                if (this.DirReady != null)
                {
                    this.DirReady(0xf000, 0, "Root", true);
                }
            }
            else if (this.DirReady != null)
            {
                this.DirReady(DirID, Parent, DirName, false);
            }
            this.rfs.Seek((long) (this.NTOffset + num2), SeekOrigin.Begin);
            ushort fileID = num3;
            while (true)
            {
                byte num5 = (byte) this.rfs.ReadByte();
                byte stringLength = (byte) (num5 & 0x7f);
                if (stringLength == 0)
                {
                    break;
                }
                string dirName = this.ReadString(this.rfs, stringLength);
                if (num5 > 0x7f)
                {
                    ushort dirID = this.ReadUShort(this.rfs);
                    this.LoadDir(dirName, dirID, DirID);
                }
                else
                {
                    this.LoadFile(dirName, fileID, DirID);
                }
                fileID = (ushort) (fileID + 1);
            }
            this.rfs.Seek(position, SeekOrigin.Begin);
        }

        private void LoadFile(string FileName, ushort FileID, ushort Parent)
        {
            long position = this.rfs.Position;
            this.rfs.Seek(this.FATOffset + (FileID * 8), SeekOrigin.Begin);
            uint num2 = this.ReadUInt(this.rfs);
            uint num3 = this.ReadUInt(this.rfs);
            if (this.FileReady != null)
            {
                this.FileReady(FileID, Parent, FileName);
            }
            this.FileIDs[FileName] = FileID;
            this.FileNames[FileID] = FileName;
            this.FileOffsets[FileID] = num2;
            this.FileSizes[FileID] = num3 - num2;
            if (num3 > this.FileLastEnd)
            {
                this.FileLastEnd = num3;
            }
            this.FileCount = (ushort) (this.FileCount + 1);
            this.rfs.Seek(position, SeekOrigin.Begin);
        }

        public void LoadROM(string FileName)
        {
            this.TimeThis = new Stopwatch();
            this.TimeThis.Start();
            this.rfs = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
            this.rfs.Seek(0x40L, SeekOrigin.Begin);
            this.NTOffset = this.ReadUInt(this.rfs);
            this.NTSize = this.ReadUInt(this.rfs);
            this.FATOffset = this.ReadUInt(this.rfs);
            this.FATSize = this.ReadUInt(this.rfs);
            this.ROMFilename = FileName;
            if (this.FileIDs == null)
            {
                this.FileIDs = new Dictionary<string, ushort>();
                this.FileNames = new Dictionary<ushort, string>();
                this.FileOffsets = new Dictionary<ushort, uint>();
                this.FileSizes = new Dictionary<ushort, uint>();
            }
            this.FileIDs.Clear();
            this.FileNames.Clear();
            this.FileOffsets.Clear();
            this.FileSizes.Clear();
            this.LoadDir("Root", 0xf000, 0);
            this.rfs.Dispose();
            this.TimeThis.Stop();
            if (this.ROMIsReady != null)
            {
                this.ROMIsReady(this.TimeThis.ElapsedMilliseconds);
            }
        }

        private string ReadString(FileStream fs, int StringLength)
        {
            if (StringLength == 0)
            {
                return "";
            }
            byte[] buffer = new byte[StringLength];
            fs.Read(buffer, 0, StringLength);
            StringBuilder builder = new StringBuilder(StringLength);
            for (int i = 0; i < StringLength; i++)
            {
                builder.Append((char) buffer[i]);
            }
            return builder.ToString();
        }

        private uint ReadUInt(FileStream fs)
        {
            byte[] buffer = new byte[4];
            fs.Read(buffer, 0, 4);
            uint num = buffer[0];
            num += (uint) (buffer[1] * 0x100);
            num += (uint) (buffer[2] * 0x10000);
            return (num + ((uint) (buffer[3] * 0x1000000)));
        }

        private ushort ReadUShort(FileStream fs)
        {
            byte[] buffer = new byte[2];
            fs.Read(buffer, 0, 2);
            return (ushort) (buffer[0] + (buffer[1] * 0x100));
        }

        public void ReplaceFile(ushort FileID, byte[] NewFile)
        {
            uint num2;
            this.rfs = new FileStream(this.ROMFilename, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
            uint num = this.FileOffsets[FileID];
            int num3 = NewFile.Length - ((int) ((ulong) this.FileSizes[FileID]));
            if (num3 > 0)
            {
                uint num4 = (uint) num3;
                foreach (ushort num5 in this.FileNames.Keys)
                {
                    uint writeThis = this.FileOffsets[num5];
                    if (writeThis > num)
                    {
                        writeThis += num4;
                        this.FileOffsets[num5] = writeThis;
                        this.rfs.Seek(this.FATOffset + (num5 * 8), SeekOrigin.Begin);
                        this.WriteUInt(this.rfs, writeThis);
                        num2 = this.ReadUInt(this.rfs) + num4;
                        this.rfs.Seek(-4L, SeekOrigin.Current);
                        this.WriteUInt(this.rfs, num2);
                    }
                }
                this.rfs.Seek((long) num, SeekOrigin.Begin);
                byte[] buffer = new byte[(this.FileLastEnd - num) + 1];
                this.rfs.Read(buffer, 0, buffer.Length);
                this.rfs.Seek((long) (num + num4), SeekOrigin.Begin);
                this.rfs.Write(buffer, 0, buffer.Length);
                this.FileLastEnd += num4;
            }
            this.rfs.Seek((long) num, SeekOrigin.Begin);
            this.rfs.Write(NewFile, 0, NewFile.Length);
            this.rfs.Seek((this.FATOffset + 4) + (FileID * 8), SeekOrigin.Begin);
            num2 = this.ReadUInt(this.rfs);
            this.rfs.Seek(-4L, SeekOrigin.Current);
            this.WriteUInt(this.rfs, Convert.ToUInt32((int) (((int) num2) + num3)));
            this.FileSizes[FileID] = (uint) NewFile.Length;
            this.rfs.Dispose();
        }

        private void WriteUInt(FileStream fs, uint WriteThis)
        {
            byte[] buffer = new byte[4];
            uint num = WriteThis;
            buffer[0] = (byte) (num & 0xff);
            num = num >> 8;
            buffer[1] = (byte) (num & 0xff);
            num = num >> 8;
            buffer[2] = (byte) (num & 0xff);
            num = num >> 8;
            buffer[3] = (byte) num;
            fs.Write(buffer, 0, 4);
        }

        public delegate void DirReadyD(int DirID, int ParentID, string DirName, bool IsRoot);

        public delegate void FileReadyD(int FileID, int ParentID, string FileName);

        public delegate void ROMIsReadyD(long TimeTakenMS);
    }
}

